#ifndef __MPSCQUEUE_H__
#define __MPSCQUEUE_H__

#include "Spark/Utils/NonCopyable.h"
#include <atomic>

template <class T>
class MPSCQueue : public NonCopyable
{
  protected:
    struct Node
    {
        T* data_;
        std::atomic<Node*> next_{nullptr};
        Node() : data_(nullptr)
        {
        }
        Node(const T& data) : data_(new T(data))
        {
        }
        Node(T&& data) : data_(new T(std::move(data)))
        {
        }
    };
    std::atomic<Node*> head_;
    std::atomic<Node*> tail_;

  public:
    MPSCQueue() : tail_(new Node)
    {
        head_ = tail_.load(std::memory_order_relaxed);
    }
    ~MPSCQueue()
    {
        T output;
        while (this->pop(output))
            ;
        Node* tmp = tail_.load(std::memory_order_relaxed);
        delete tmp;
    }

  public:
    void push(const T& data)
    {
        Node* n{new Node(data)};
        Node* tmptail{tail_.exchange(n, std::memory_order_acq_rel)};

        tmptail->next_.store(n, std::memory_order_release);
    }

    void push(T&& data)
    {
        Node* n{new Node(std::move(data))};
        Node* tmptail{tail_.exchange(n, std::memory_order_acq_rel)};
        tmptail->next_.store(n, std::memory_order_release);
    }

    bool pop(T& output)
    {
        Node* head = head_.load(std::memory_order_relaxed);

        Node* next = head->next_.load(std::memory_order_acquire);

        if (next == nullptr)
        {
            return false;
        }
        output = std::move(*(next->data_));
        delete next->data_;

        head_.store(next, std::memory_order_release);

        delete head;
        return true;
    }

    bool empty()
    {
        Node* head = head_.load(std::memory_order_relaxed);
        Node* next = head->next_.load(std::memory_order_acquire);
        return next == nullptr;
    }
};

#endif